﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;

using Tradex;
using Tradex.Market;
using Tradex.MarketStream;
using Tradex.Messages;
using Tradex.Trading;

namespace Tradex.Connectivity.DataLog {

    /// <summary>
    /// The DataLogWriter is responsible for creating all the data log files that the wrapper can
    /// create. It basically creates the them as needed, taking the event input from the
    /// actual connector and writing it out to the proper file.
    /// 
    /// This class is mostly necessary as log files regularly "revolve" (every hour) into new files,
    /// and this class handles all the relevant elements of the dumpfile mechanism transparently.
    /// 
    /// The manager creates, in the folder given as parameter, the following dump files (names as examples):
    /// * Instrument-20090922.txt - instrument update information, coded by date and exchange.
    /// * Activity-20090922-14-CME.txt - market update information, coded by time and exchange.
    /// * Order-20090922.txt - order flow information, coded by date only.
    /// * Execution-20090922.txt - execution and bust info, coded by date and hour.
    /// 
    /// Market updates are time and exchange coded as they are processed by exchange and the amount of data flowing
    /// through the system is terrific. For every other type, the coding is only by date and exchange.
    /// 
    /// Time and day coded file are to be used carefully. In general, the timestamp on the file will be the
    /// timestamp used for writing the data. Internal data has an additional field that contains the "data internal"
    /// timestamp. Software parsing the files thus should be careful about historical information mixed with current
    /// one (which should be visible on older timestamps).
    /// </summary>
    public class DataLogWriter : IDisposable {

        /// <summary>
        /// Constructor taking the folder to use as data store as parameter.
        /// </summary>
        /// <param name="folder"></param>
        public DataLogWriter(string folder) {
            if (!Directory.Exists (folder)) {
                throw new ArgumentException ("Folder not found", "folder");
            }
            _Folder = folder;
        }

        string _Folder;

        const string ACTIVITYFILENAME_FORMATSTRING = "Activity-{0:yyyyMMdd-HHmm}-{1}.txt";

        const string EXECUTIONFILENAME_FORMATSTRING = "Execution-{0:yyyyMMdd}.txt";

        const string INSTRUMENTFILENAME_FORMATSTRING = "Instrument-{0:yyyyMMdd}.txt";

        const string ORDERFILENAME_FORMATSTRING = "Order-{0:yyyyMMdd}.txt";

        /// <summary>
        /// This dictionary contains a ActivityFileInfo structure for every market. The market is in string
        /// form and given in the first generic parameter.
        /// </summary>
        Dictionary<string, ActivityFileInfo> _ActivityFiles = new Dictionary<string, ActivityFileInfo>();

        /// <summary>
        /// This MarketFileInfo is the last file that was written to. It is used as a shortcut for multiple
        /// updates for the same market - it is used first, and only if markets do not match the _MarketFiles
        /// dictionary is consulted.
        /// </summary>
        ActivityFileInfo _LastActivityFiles = null;

        /// <summary>
        /// The ExeuctionFileInfo is the last file that was written to.
        /// </summary>
        ExecutionFileInfo _LastExecutionFile;

        /// <summary>
        /// The InstrumentFileInfo is the last file that was written to.
        /// </summary>
        InstrumentFileInfo _LastInstrumentFile;
        /// <summary>
        /// The OrderFileInfo is the last file that was written to.
        /// </summary>
        OrderFileInfo _LastOrderFile;

        /// <summary>
        /// Processing method accepting a Message. It will - if applicable - write the message
        /// content out into the proper files (s).
        /// </summary>
        /// <param name="message"></param>
        public void Process(Tradex.Messages.Message message) {
            if (message == null) {
                return;
            }
            switch (message.Code) {
                case Tradex.Messages.MessageCode.InstrumentActivityUpdate:
                    InstrumentActivityUpdateMessage activity = (InstrumentActivityUpdateMessage)message;
                    if (activity.RequestCode != null) {
                        // We dont want out of band repeated data, or historical requests handled here.
                        break;
                    }
                    ProcessMessage(activity);
                    break;
                case Tradex.Messages.MessageCode.InstrumentUpdate:
                    InstrumentUpdateMessage update = (InstrumentUpdateMessage)message;
                    ProcessMessage(update);
                    break;
                case Tradex.Messages.MessageCode.OrderUpdate:
                    OrderUpdateMessage orderUpdate = (OrderUpdateMessage)message;
                    ProcessMessage(orderUpdate);
                    break;

            }
        }

        void ProcessMessage(InstrumentActivityUpdateMessage update) {
            InstrumentKey instrument = update.InstrumentKey;
            Activity Activity = update.Activity;

            // We need to find the proper market file... COULD be the same as last time... or not...
            ActivityFileInfo info = _LastActivityFiles;
            if (info == null || info.Market != instrument.Market) {
                _ActivityFiles.TryGetValue(instrument.Market, out info);
            }
            // If we got a MarketFileInfo, we check whether it is expired.
            if (info != null) {
                if (info.EndDateTime < Activity.Timestamp) {
                    // File expired, let's redo it.
                    info.Writer.Dispose();
                    info = null;
                }
            }
            // If we have no MarketFileInfo for now, we create a new file and hook it up.
            if (info == null) {
                info = new ActivityFileInfo();
                info.Market = instrument.Market;
                DateTime startDateTime = new DateTime(
                    Activity.Timestamp.Year,
                    Activity.Timestamp.Month,
                    Activity.Timestamp.Day,
                    Activity.Timestamp.Hour, 0, 0,
                    DateTimeKind.Utc
                );
                // We now move to end of that - plus one hour, minus one tick
                info.EndDateTime = startDateTime.AddHours(1);
                info.EndDateTime = info.EndDateTime.AddTicks(-1);

                string file = Path.Combine(
                    _Folder,
                    MakeFileName(ACTIVITYFILENAME_FORMATSTRING, startDateTime, instrument.Market)
                );
                info.Writer = new StreamWriter (file);
                _ActivityFiles[instrument.Market] = info;
            }
            // Finally: We write out the data...
            info.Writer.WriteActivityLogEntry(update);

            // And set the _MarketFIlesLastInfo variable to the one used.
            _LastActivityFiles = info;
        }

        void ProcessMessage(InstrumentUpdateMessage update) {
            InstrumentFileInfo info = _LastInstrumentFile;
            if (info != null && info.EndDateTime < DateTime.UtcNow) {
                info.Writer.Dispose();
                info = null;
            }
            if (info == null) {
                info = new InstrumentFileInfo();
                DateTime now = DateTime.UtcNow;
                DateTime startDateTime = new DateTime(
                    now.Year,
                    now.Month,
                    now.Day,
                    now.Hour, 0, 0,
                    DateTimeKind.Utc
                );
                // We now move to end of that - plus one hour, minus one tick
                info.EndDateTime = startDateTime.AddHours(1);
                info.EndDateTime = info.EndDateTime.AddTicks(-1);

                string file = Path.Combine(
                    _Folder,
                    MakeFileName(INSTRUMENTFILENAME_FORMATSTRING, startDateTime, update.InstrumentKey.Market)
                );
                info.Writer = new StreamWriter (file);
                _LastInstrumentFile = info;
            }
            info.Writer.Write(update);
        }

        void ProcessMessage(OrderUpdateMessage update) {
            OrderUpdate progress = update.Progress;
            if (progress != null) {
                OrderFileInfo info = _LastOrderFile;
                if (info != null && info.EndDateTime < DateTime.UtcNow) {
                    info.Writer.Dispose();
                    info = null;
                }
                if (info == null) {
                    info = new OrderFileInfo ();
                    DateTime now = DateTime.UtcNow;
                    DateTime startDateTime = new DateTime(
                        now.Year,
                        now.Month,
                        now.Day,
                        now.Hour, 0, 0,
                        DateTimeKind.Utc
                    );
                    // We now move to end of that - plus one hour, minus one tick
                    info.EndDateTime = startDateTime.AddHours(1);
                    info.EndDateTime = info.EndDateTime.AddTicks(-1);

                    string file = Path.Combine(
                        _Folder,
                        MakeFileName(ORDERFILENAME_FORMATSTRING, startDateTime, String.Empty)
                    );
                    info.Writer = new StreamWriter(file);
                }
                info.Writer.WriteOrderLogEntry(update);
                
                _LastOrderFile = info;
            }

            OrderExecution execution = update.Execution;
            if (execution != null) {
                ExecutionFileInfo info = _LastExecutionFile;
                if (info != null && info.EndDateTime < DateTime.UtcNow) {
                    info.Writer.Dispose();
                    info = null;
                }
                if (info == null) {
                    info = new ExecutionFileInfo();
                    DateTime now = DateTime.UtcNow;
                    DateTime startDateTime = new DateTime(
                        now.Year,
                        now.Month,
                        now.Day,
                        now.Hour, 0, 0,
                        DateTimeKind.Utc
                    );
                    // We now move to end of that - plus one hour, minus one tick
                    info.EndDateTime = startDateTime.AddHours(1);
                    info.EndDateTime = info.EndDateTime.AddTicks(-1);

                    string file = Path.Combine(
                        _Folder,
                        MakeFileName(EXECUTIONFILENAME_FORMATSTRING, startDateTime, String.Empty)
                    );
                    info.Writer = new StreamWriter(file);
                }
                info.Writer.WriteExecutionLogEntry(update);

                _LastExecutionFile = info;
            }
        }

        /// <inheritdoc />
        public void Dispose() {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        void Dispose(bool disposing) {
            if (disposing) {
                foreach (ActivityFileInfo mfi in _ActivityFiles.Values) {
                    mfi.Writer.Dispose();
                }
                _ActivityFiles = null;
            }
        }

        static string MakeFileName(string formatString, DateTime dateTime, string Market) {
            return String.Format(CultureInfo.InvariantCulture, formatString, dateTime, Market);
        }

    }
}
